/***************************************************************
 *  Copyright (c) 2007 by GroupMe! Team (www.groupme.net)
 *  All rights reserved.
 *
 *  This file is part of the GroupMe! Project. Source code of 
 *  this project is closed and redistribution of this code is
 *  prohibited. 
 *  
 *  Contact: http://www.groupme.net
 *
 *  This copyright notice MUST APPEAR in all copies of the file!
 ***************************************************************/
package net.groupme.controller.ajax;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;

import net.groupme.controller.model.GroupMeSearchResult;
import net.groupme.controller.model.ResourceVisualizationBean;
import net.groupme.controller.model.SearchResult;
import net.groupme.exceptions.AddingResourceFailedException;
import net.groupme.exceptions.UpdateFailedException;
import net.groupme.exceptions.search.SearchEngineException;
import net.groupme.group.GroupManager;
import net.groupme.gui.constants.VIEW_VOCABULARY;
import net.groupme.gui.rendering.RenderingUtility;
import net.groupme.model.Group;
import net.groupme.model.MODEL_CONSTANTS;
import net.groupme.model.Resource;
import net.groupme.model.ResourceVisualization;
import net.groupme.model.Tag;
import net.groupme.model.rdf.MetadataUtility;
import net.groupme.search.SEARCH_CONFIGURATION;
import net.groupme.search.SearchManager;
import net.groupme.search.flickr.FlickrSearchEngine;
import net.groupme.search.google.AjaxSearch;
import net.groupme.search.google.GoogleSearchEngine;
import net.groupme.search.manualadd.ManualAddOfRessources;
import net.groupme.search.rss.RssSearchEngine;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

/**
 * created on 25/06/2007
 * 
 * This is the general search controller for aggregating new resources into
 * GroupMe!
 * 
 * 
 * @author Fabian Abel, <a href="mailto:abel@l3s.de">abel@l3s.de</a>
 * @author last edited by: $Author: fabian $
 * 
 * @version $Revision: 1.36 $ $Date: 2010-02-06 13:16:47 $
 */
public class ResourceAggregationAjaxController implements
		ApplicationContextAware {
	
	/** We have a problem with the Google API!!! TODO Re-implement search for (1) Web resources, (2) Videos, (3) RSS feeds */
	public static boolean PROBLEM_GOOGLE_API_NOT_WORKING = true;

	/** Logger for this class and subclasses */
	protected final Log logger = LogFactory.getLog(getClass());

	/** the application context */
	private ApplicationContext applicationContext = null;
	
	
	/** indicates that a given URL should be opened with a News Reader. As the Informa-API cannot handle
	 * this protocol we should simply try to replace "feed://" with "http://"
	 */ 
	private static final String FEED_PROTOCOL = "feed://";
	
	/** HTTP protocol indicator ("http://") */
	private static final String HTTP_PROTOCOL = "http://";

	/**
	 * Set the ApplicationContext that this object runs in.
	 * 
	 * @param applicationContext
	 *            the current {@link ApplicationContext}
	 */
	public void setApplicationContext(ApplicationContext applicationContext)
			throws BeansException {
		this.applicationContext = applicationContext;
	}

	/** the actual common search result list */
	private List<SearchResult> actualSearchResultList = new ArrayList<SearchResult>();
	
	/** the actual html search result list */
	private List<SearchResult> actualHTMLSearchResultList = new ArrayList<SearchResult>();
	
	/** the actual image search result list */
	private List<SearchResult> actualImageSearchResultList = new ArrayList<SearchResult>();
	
	/** the actual rss search result list */
	private List<SearchResult> actualRSSSearchResultList = new ArrayList<SearchResult>();
	
	/** the actual multimedia search result list */
	private List<SearchResult> actualMMSearchResultList = new ArrayList<SearchResult>();
	
	/** the actual GroupMe! groups search result list */
	private List<SearchResult> actualGMGSearchResultList = new ArrayList<SearchResult>();
	
	/** the actual GroupMe! resources search result list */
	private List<SearchResult> actualGMRSearchResultList = new ArrayList<SearchResult>();

	/**
	 * Performs a search for resources using the given keyword string (could
	 * contain multiple keywords) and the search engine which corresponds to the
	 * given <i>engineIndicator</i>
	 * 
	 * @param keyword
	 *            the keyword(s)
	 * @param engineIndicator
	 *            a String indicating which search engine to be used
	 * @return array containing search results ({@link SearchResult}) or null
	 *         if the corresponding search engine could not be executed
	 *         correctly.
	 */
	public SearchResult[] searchForResources(String keywords,
			String engineIndicator) {
		logger.info("Searching for " + keywords + " at " + engineIndicator);

		// normally this could be realized highly generic, but I'm not sure at
		// the moment
		// which aparameters have to be passed to the other engines (in the
		// worst case we should simply
		// pass a Parameter-Value-Map)...
		// Flickr:
		if (SEARCH_CONFIGURATION.SOURCE_FLICKR.equals(engineIndicator)) {
			try {
				actualImageSearchResultList = new FlickrSearchEngine()
						.searchForFlickrPhotos(
								keywords,
								SEARCH_CONFIGURATION.AGGREGATION_MAXIMUM_NUMBER_OF_FLICKER_SEARCH_RESULTS);
				SearchResult[] asArray = actualImageSearchResultList
						.toArray(new SearchResult[actualImageSearchResultList.size()]);
				logger.info("Searching for " + keywords + " at " + engineIndicator + " finished.");
				return asArray;
			} catch (SearchEngineException e) {
				logger.error("Panic! Flickr Search Engine had problems.", e);
			}
		}
		// Google:
		else if (SEARCH_CONFIGURATION.SOURCE_GOOGLE.equals(engineIndicator)) {
			
			actualHTMLSearchResultList = new AjaxSearch()
					.searchGoogle(
							keywords,
							"html",
							SEARCH_CONFIGURATION.AGGREGATION_MAXIMUM_NUMBER_OF_SEARCH_RESULTS);
			if(actualHTMLSearchResultList == null || actualHTMLSearchResultList.isEmpty()){
				actualHTMLSearchResultList = new ArrayList<SearchResult>();
			}
			SearchResult[] asArray = actualHTMLSearchResultList
					.toArray(new SearchResult[actualHTMLSearchResultList.size()]);
			logger.info("Searching for " + keywords + " at " + engineIndicator + " finished.");
			return asArray;
		}
		// Google for youtube:
		else if (SEARCH_CONFIGURATION.SOURCE_YOUTUBE.equals(engineIndicator)) {
			
			actualMMSearchResultList = new AjaxSearch()
					.searchGoogleForVideos(
							keywords,
							"html",
							SEARCH_CONFIGURATION.AGGREGATION_MAXIMUM_NUMBER_OF_SEARCH_RESULTS);
			for (SearchResult res : actualMMSearchResultList) {
				//Result ID == youtube vid URL
				String url = res.getUrl();
				if(url != null && url.contains("youtube")){
					if(url.contains("/v/")){
						res.setResultID(url);
					}else{
						String vidId = url.toString().substring(url.toString().indexOf("v=")+2);
						if(vidId.indexOf("&") != -1){
							vidId = vidId.substring(0, vidId.indexOf("&"));
						}
						res.setResultID("http://www.youtube.com/v/" + vidId);
					}
					//res.setResultID("youtube" + res.getResultID());
					res.setType(MODEL_CONSTANTS.RESOURCE_TYPE_SHOCKWAVE);
				}
			}
			SearchResult[] asArray = actualMMSearchResultList
					.toArray(new SearchResult[actualMMSearchResultList.size()]);
			logger.info("Searching for " + keywords + " at " + engineIndicator + " finished.");
			return asArray;
		}
		// GroupMe! groups:
		else if (SEARCH_CONFIGURATION.SOURCE_GROUPME_GROUPS
				.equals(engineIndicator)) {
			logger.info("Searching for GroupMe-Groups...");
			SearchManager searchManager = (SearchManager) applicationContext
					.getBean(VIEW_VOCABULARY.GROUPME_SEARCH_MANGER,
							SearchManager.class);
			searchManager.resetSearchHistory();
			searchManager.setLastSearchFor(keywords);
			actualGMGSearchResultList.clear();
			try {
				List<Group> suitingGroups = searchManager.searchForGroups(keywords, false);
				searchManager.setSuitingGroups(suitingGroups);
				for (Group group : suitingGroups) {
					actualGMGSearchResultList.add(convertToSearchResult(group));
				}
				SearchResult[] asArray = actualGMGSearchResultList
						.toArray(new SearchResult[actualGMGSearchResultList.size()]);
				logger.info("Searching for " + keywords + " at " + engineIndicator + " finished."
						+ " Found " + asArray.length + " GroupMe Groups.");
				return asArray;
			} catch (SearchEngineException e) {
				e.printStackTrace();
				logger.error(
						"An error occured during internal search for groups.",
						e);
			}
		}
		// GroupMe! resources:
		else if (SEARCH_CONFIGURATION.SOURCE_GROUPME_RESOURCES
				.equals(engineIndicator)) {
			logger.info("Searching for GroupMe-Resources...");
			SearchManager searchManager = (SearchManager) applicationContext
					.getBean(VIEW_VOCABULARY.GROUPME_SEARCH_MANGER,
							SearchManager.class);
			searchManager.resetSearchHistory();
			searchManager.setLastSearchFor(keywords);
			actualGMRSearchResultList.clear();
			try {
				// here we search only for resources (groups are excluded)
				List<Resource> suitingResources = searchManager
						.searchForResources(keywords, false, true);
				for (Resource resource : suitingResources) {
					actualGMRSearchResultList.add(convertToSearchResult(resource));
				}
				SearchResult[] asArray = actualGMRSearchResultList
						.toArray(new SearchResult[actualGMRSearchResultList.size()]);
				logger.info("Searching for " + keywords + " at " + engineIndicator + " finished."
						+ " Found " + asArray.length + " GroupMe Resources.");
				return asArray;
			} catch (SearchEngineException e) {
				e.printStackTrace();
				logger
						.error(
								"An error occured during internal search for resources.",
								e);
			}
		// RSS resources:
		} else if (SEARCH_CONFIGURATION.SOURCE_RSSSEARCH
				.equals(engineIndicator)  && !PROBLEM_GOOGLE_API_NOT_WORKING) {
			actualRSSSearchResultList = new RssSearchEngine()
					.searchRss(
							keywords,
							SEARCH_CONFIGURATION.AGGREGATION_MAXIMUM_NUMBER_OF_SEARCH_RESULTS);
			SearchResult[] asArray = actualRSSSearchResultList
					.toArray(new SearchResult[actualRSSSearchResultList.size()]);
			logger.info("Searching for " + keywords + " at " + engineIndicator + " finished.");
			return asArray;
		}
		SearchResult[] asArray = actualHTMLSearchResultList
				.toArray(new SearchResult[actualHTMLSearchResultList.size()]);
		return asArray;
	}

	/**
	 * Performs a search for resources using the given keyword string (could
	 * contain multiple keywords) and the search engine which corresponds to the
	 * given <i>engineIndicator</i>
	 * 
	 * @param keyword
	 *            the keyword(s)
	 * @param engineIndicator
	 *            a String indicating which search engine to be used
	 * @return array containing search results ({@link SearchResult}) or null
	 *         if the corresponding search engine could not be executed
	 *         correctly.
	 */
	public SearchResult[] searchResources(String keywords) {
		logger.info("Searching for " + keywords );

		// reset actual search result:
		actualSearchResultList = new ArrayList<SearchResult>();

		// normally this could be realized highly generic, but I'm not sure at
		// the moment
		// which aparameters have to be passed to the other engines (in the
		// worst case we should simply
		// pass a Parameter-Value-Map)...
		
		// Google:
		
			actualSearchResultList.addAll(new GoogleSearchEngine()
					.searchGoogle(
							keywords,
							"html",
							SEARCH_CONFIGURATION.AGGREGATION_MAXIMUM_NUMBER_OF_SEARCH_RESULTS));
		
		// Google filetype:swf:
		
			actualSearchResultList.addAll(new GoogleSearchEngine()
					.searchGoogle(
							keywords,
							"swf",
							SEARCH_CONFIGURATION.AGGREGATION_MAXIMUM_NUMBER_OF_SEARCH_RESULTS));
		
		// Flickr:
		try {
			actualSearchResultList.addAll(new FlickrSearchEngine()
					.searchForFlickrPhotos(
							keywords,
							SEARCH_CONFIGURATION.AGGREGATION_MAXIMUM_NUMBER_OF_FLICKER_SEARCH_RESULTS));
		} catch (SearchEngineException e) {
			logger.error("Panic! Flickr Search Engine had problems.", e);
		}
		// GroupMe! groups:
		logger.info("Searching for GroupMe-Groups...");
		SearchManager searchManager = (SearchManager) applicationContext
				.getBean(VIEW_VOCABULARY.GROUPME_SEARCH_MANGER,
						SearchManager.class);
		try {
			List<Group> suitingGroups = searchManager.searchForGroups(
					keywords, false);
			for (Group group : suitingGroups) {
				actualSearchResultList.add(convertToSearchResult(group));
			}
		} catch (SearchEngineException e) {
			e.printStackTrace();
			logger.error(
					"An error occured during internal search for groups.",
					e);
		}
		// GroupMe! resources:
		logger.info("Searching for GroupMe-Resources...");
		searchManager = (SearchManager) applicationContext
				.getBean(VIEW_VOCABULARY.GROUPME_SEARCH_MANGER,
						SearchManager.class);
		try {
			// here we search only for resources (groups are excluded)
			List<Resource> suitingResources = searchManager
					.searchForResources(keywords, false, true);
			for (Resource resource : suitingResources) {
				actualSearchResultList.add(convertToSearchResult(resource));
			}
		} catch (SearchEngineException e) {
			e.printStackTrace();
			logger
					.error(
							"An error occured during internal search for resources.",
							e);
		}
	// RSS resources:
		actualSearchResultList.addAll(new RssSearchEngine()
				.searchRss(
						keywords,
						SEARCH_CONFIGURATION.AGGREGATION_MAXIMUM_NUMBER_OF_SEARCH_RESULTS));
		SearchResult[] asArray = actualSearchResultList
				.toArray(new SearchResult[actualSearchResultList.size()]);
		return asArray;
	}
	
	/**
	 * This method returns a SearchResultList containing the SearchResult which
	 * was manually added.
	 * 
	 * @return the actualSearchResultList
	 * @throws UnsupportedEncodingException 
	 */
	public ResourceVisualizationBean addResourceManual(String url, String titel,
			String description) throws UnsupportedEncodingException {
		GroupManager groupManager = (GroupManager) applicationContext.getBean(VIEW_VOCABULARY.GROUP_MANGER, GroupManager.class);
		if (groupManager.getManager() == null){
			return null;
		}
		logger.info("Adding a Ressource manually");
		// reset actual search result:
		actualSearchResultList = new ArrayList<SearchResult>();
		// Manual Add :
		logger.info("Adding Resource Manual by its URL");
		// Prüfen ob http:// vor dem String steht, wenn nicht hinzufügen
		if (!url.startsWith(HTTP_PROTOCOL)) {
			url = HTTP_PROTOCOL + url;
		}
		// Checks the URL and in case of a malformed URL an defined SearchResult
		// is returned
		try {
			URL realurl = new URL(url);
			if (realurl.openConnection().getContent() == null) {
				logger.error("URL with no contend detected" + url);
				return null;
			}
		} catch (MalformedURLException e) {
			logger.error("Malformed URL detected" + url);
			return null;
		} catch (IOException e) {
			logger.error("IOExeption while trying to open the URL" + url);
			return null;
		}
		actualSearchResultList = new ManualAddOfRessources().addResourceManual(
				url, titel, description);
		
		ResourceAggregationAjaxController aggregator = (ResourceAggregationAjaxController)applicationContext.getBean(VIEW_VOCABULARY.RESSOURCE_AGGREGATION, ResourceAggregationAjaxController.class);
		SearchResult resourceToAdd = actualSearchResultList.get(0);
		logger.info("Resource to be added: " + resourceToAdd);
		
		//1. create resource (if it is not already a GroupMe! resource) and add it to the group
		ResourceVisualization resVisu = null;
		try {
			if(resourceToAdd instanceof GroupMeSearchResult){
				logger.info("Adding GroupMe! resource (or group) to group:" + resourceToAdd.toString());
				resVisu = groupManager.addGroupMeResource(((GroupMeSearchResult)resourceToAdd).getResource());
			}else{
				logger.info("Adding non GroupMe! resource (or group) to group:" + resourceToAdd.toString());
				resVisu = groupManager.addResource(resourceToAdd.getName(), resourceToAdd.getDescription(), resourceToAdd.getType(), resourceToAdd.getUrl());
			}
		} catch (AddingResourceFailedException e) {
			e.printStackTrace();
			logger.error("Adding GroupMe! resource failed!!");
		}
		
		//2. extraction of Metadata and putting the Statements to the Resource
		if (!MetadataUtility.extractAndAdd(resourceToAdd, resVisu.getResource())){
			logger.warn("Failed to extract and add Metadata of Resource: " + resVisu.getResource().getURI().toString());
		}
		//3. detect suiting renderer:
		if(resourceToAdd instanceof GroupMeSearchResult){
			resVisu.setRenderer(RenderingUtility.suggestRenderer(((GroupMeSearchResult)resourceToAdd).getResource().getType()));
		}else{
			resVisu.setRenderer(RenderingUtility.suggestRenderer(resourceToAdd.getType()));
		}
		
		//4. let Spring create the corresponding Renderer and then return the resourceVisualization in an HTML format
// depricated
//		Renderer resourceRenderer = (Renderer) applicationContext.getBean(resVisu.getRenderer(), Renderer.class);
//		HtmlFormattedElement result = new HtmlFormattedElement(resVisu.getURI().toString(), resourceRenderer.renderAsHTML(resVisu, 500, 600, 100));
//		
//		logger.info("HTML-Formatted Resource: " + result.getHtml());
		
//		5. save Position and Size
		try {
			resVisu.setPositionX(210);
			resVisu.setPositionY(140);
			resVisu.setWidth(246);
			resVisu.setHeight(1455);
			resVisu.setZAxis(1001);
		} catch (UpdateFailedException e) {
			logger.debug("Update failed, maybe the Resource was just removed.");
			return null;
		}
		
		
//		Estellung des Tag-Arrays
		List<Tag> tagList = (List) resVisu.getResource().getTags(groupManager.getGroup());
		String[] tagArray = new String[tagList.size()];
		String[] tagKeywordArray = new String[tagList.size()];
		for (int j = 0; j < tagList.size(); j++) {
			String keyword = tagList.get(j).getKeyword();
			String URLkeyword = keyword.replaceAll(" ", "_");	
			URLkeyword = URLEncoder.encode(URLkeyword, "UTF-8");
			
			//tagArray[j] = tagList.get(j).getKeyword();
			tagArray[j] = URLkeyword;
			tagKeywordArray[j] = keyword;
		}
//		Erstellung der Bean f�r R�ckgabe		
		ResourceVisualizationBean resourceVisualizationBean = new ResourceVisualizationBean(
				resVisu.getResource().getDescription(), 
				resVisu.getResource().getId(), 
				resVisu.getResource().getName(),
				157,
				220,
				165,
				91,
				1001,
				resVisu.getURI().toString(),
				resVisu.getResource().getURL().toString(),
				resVisu.getRenderer(),
				tagArray,
				tagKeywordArray
				);
		
		return resourceVisualizationBean;
	}

	/**
	 * This method returns a SearchResultList containing the SearchResult which
	 * was manually added (just one RSS feed).
	 * 
	 * @return the actualSearchResultList
	 */
	public SearchResult[] addRssManual(String url) {
		logger.info("Adding a RSS Ressource manually");
		
		//As the Informa-API cannot handle feed-Protocol we simply try to replace "feed://" with "http://"
		if(url != null && url.startsWith(FEED_PROTOCOL)){
			url = url.replaceFirst(FEED_PROTOCOL, HTTP_PROTOCOL);
		}
		
		// reset actual search result:
		actualSearchResultList = new ArrayList<SearchResult>();
		// Manual Add :
		// Prüfen ob http:// vor dem String steht, wenn nicht hinzufügen
		if (!url.startsWith(HTTP_PROTOCOL)) {
			url = HTTP_PROTOCOL + url;
		}
		logger.info("Adding RSS Resource Manual by its URL");
		// Checks the URL and in case of a malformed URL an defined SearchResult
		// is returned
		try {
			URL realurl = new URL(url);
			if (realurl.openConnection().getContent() == null) {
				logger.error("URL with no contend detected" + url);
				return new SearchResult[0];
			}
		} catch (MalformedURLException e) {
			logger.error("Malformed URL detected" + url);
			return new SearchResult[0];
		} catch (IOException e) {
			logger.error("IOExeption while trying to open the URL" + url);
			return new SearchResult[0];
		}

		actualSearchResultList = new RssSearchEngine().addRss(url);
		SearchResult[] asArray = actualSearchResultList
				.toArray(new SearchResult[actualSearchResultList.size()]);
		return asArray;
	}

	/**
	 * This method returns the actualSearchResultList of a SearchResult object.
	 * 
	 * @return the actualSearchResultList
	 */
	public List<SearchResult> getActualSearchResultList() {
		actualSearchResultList = new ArrayList<SearchResult>();
		
		if(actualGMGSearchResultList != null && !actualGMGSearchResultList.isEmpty()){
			actualSearchResultList.addAll(actualGMGSearchResultList);
		}
		if(actualGMRSearchResultList != null && !actualGMRSearchResultList.isEmpty()){
			actualSearchResultList.addAll(actualGMRSearchResultList);
		}
		if(actualHTMLSearchResultList != null && !actualHTMLSearchResultList.isEmpty()){
			actualSearchResultList.addAll(actualHTMLSearchResultList);
		}
		if(actualImageSearchResultList != null && !actualImageSearchResultList.isEmpty()){
			actualSearchResultList.addAll(actualImageSearchResultList);
		}
		if(actualRSSSearchResultList != null && !actualRSSSearchResultList.isEmpty()){
		actualSearchResultList.addAll(actualRSSSearchResultList);
		}
		if(actualMMSearchResultList != null && !actualMMSearchResultList.isEmpty()){
			actualSearchResultList.addAll(actualMMSearchResultList);
		}
		return actualSearchResultList;
	}

	/**
	 * Returns the {@link SearchResult} which corresponds to the given ID by
	 * searching within the actual search result list of a user.
	 * 
	 * @param id
	 *            the id of the {@link SearchResult} instance
	 * @return the {@link SearchResult} instance
	 */
	public SearchResult getSearchResultForID(String id) {
		for (SearchResult res : this.getActualSearchResultList()) {
			logger.info("Looking for " + id + " found " + res.getResultID());
			if (id.equals(res.getResultID())) {
				return res;
			}
		}
		return null;
	}

	/**
	 * Wraps a {@link Resource} into a {@link GroupMeSearchResult}
	 * 
	 * @param resource
	 *            the {@link Resource} to be wrapped
	 * @return the wrapped resource
	 */
	private GroupMeSearchResult convertToSearchResult(Resource resource) {
		GroupMeSearchResult result = new GroupMeSearchResult();
		result.setName(resource.getName());
		result.setDescription(resource.getDescription());
		if (resource.getURL() != null) {
			result.setUrl(resource.getURL().toString());
		}
		result.setResource(resource);
		result.setResultID(resource.getId());
		if (resource instanceof Group) {
			result.setType(MODEL_CONSTANTS.RESOURCE_TYPE_GROUP);
		} else {
			result.setType(MODEL_CONSTANTS.RESOURCE_TYPE_GROUPME_RESOURCE);
		}
		return result;
	}
}
